//----------------------------------------------------------------------------
//  ,-.
// /_\___ ,   Ninja Framework
// '  ___\    (C) Riccardo Capra - http://riccar.do
//  //   \\
//----------------------------------------------------------------------------
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
//----------------------------------------------------------------------------

#ifndef NJ_EVENTS_SYSTEM_H
#	define NJ_EVENTS_SYSTEM_H

#	include "NjNinjaSyncLib.h"
#	include "NjGenericStaticInterface.h"
#	include "NjFundamentalLimits.h"

	/// \brief Interface to kernel event features
	class NjEventsSystem : public NjGenericStaticInterface
	{
	public:
		/// \brief Opaque class reppresenting the kernel event
		class Event;

		/// \brief Type used to define timings in milliseconds
		typedef NjUint TimeMs;

		/// \brief TryWait call possible results
		enum TryResult
		{
			/// \brief TryWait call failed
			TRY_FAILURE,

			/// \brief TryWait call succeeded
			TRY_SUCCESS,

			/// \brief TryWait call timed out
			TRY_TIMEOUT,
		};

		/// \brief Parameter to be passed to TryWait if you do now want delays
		static const TimeMs NO_WAIT = 0;

		/// \brief Parameter to be passed to TryWait if you want to wait until the kernel event is set
		static const TimeMs WAIT_FOREVER = NJ_MAX_UINT;

		/// \brief Invalid thread id value
		static const NjUint INVALID_THREAD_ID = NJ_MAX_UINT;

		/// Abstract class reppresenting the NjEventsSystem implementation
		class Implementation : public NjGenericStaticInterface::Implementation
		{
			friend class NjEventsSystem;

		public:
			NJ_TRIVIAL_VIRTUAL_DESTRUCTOR(Implementation);

		protected:
			NJ_TRIVIAL_DEFAULT_CONSTRUCTOR(Implementation);

			/// \brief Creates a kernel event
			///
			/// \param bSet If is set to NJ_TRUE the event is created in not-blocking state. If is set to NJ_FALSE the event is created in blocking state
			/// \return The created kernel event or NJ_NULL on internal error/no more kernel objects available
			virtual Event * CreateEvent(NjBool bSet) = 0;

			/// \brief Waits on a previously created kernel event
			///
			/// If the kernel event is set to non-blocking state before \em uTimeMs expires, the function will return TRY_SUCCESS.
			/// If the kernel event is still in blocking state after \em uTimeMs expires, the function will return TRY_TIMEOUT. 
			/// In case of internal error TRY_FAILURE will be returned. 
			/// If \m uTimeMs is set to NjEventsSystem::WAIT_FOREVER the function will never time out.
			/// This means that in this case just TRY_SUCCESS or TRY_FAILURE can be returned
			/// \param pEvent the event to wait for
			/// \param uTimeMs the maximum wait time expressed in milliseconds
			virtual TryResult TryWaitEvent(Event * pEvent, TimeMs uTimeMs) = 0;

			/// \brief Sets a previously created kernel event \em pEvent into not-blocking state
			///
			/// This function can be also called when \em pEvent is already in not-blocking state. 
			/// In this case nothing will happen and the function will return NJ_TRUE
			/// \return The function will return NJ_TRUE on success or NJ_FALSE in case of internal error
			virtual NjBool SetEvent(Event * pEvent) = 0;

			/// \brief Sets a previously created kernel event `pEvent` into blocking state
			///
			/// This function can be also called when \em pEvent is already in blocking state.
			// In this case nothing will happen and the function will return NJ_TRUE
			/// \return The function will return NJ_TRUE on success or NJ_FALSE in case of internal error
			virtual NjBool UnsetEvent(Event * pEvent) = 0;

			/// \brief Destroys a previously created kernel event \em pEvent
			///
			/// Once this function is called the pointer \em pEvent cannot be any more used. The usage of such pointer has undefined behaviour
			/// \return The function will return NJ_TRUE on success or NJ_FALSE in case of internal error
			virtual NjBool DestroyEvent(Event * pEvent) = 0;

			/// \brief This function will return in \em uDebugId the current thread id
			/// 
			/// This Id is used just for debugging purposes
			/// \return The function will return NJ_TRUE on success or NJ_FALSE in case of internal error
			virtual NjBool GetCurrentThreadDebugId(NjUint & uDebugId) = 0;

			/// \brief Returns in \em uTimeMs the elapsed time in milliseconds
			///
			/// This function is used just to compute differences in time and detect when threads time out on TryXxx methods of the provided synchronization objects
			/// \return The function will return NJ_TRUE on success or NJ_FALSE in case of internal error
			virtual NjBool GetTimeMs(TimeMs & uTimeMs) = 0;

			/// \brief Returns in \em uSpinCount the spin count.
			///
			/// The spin count is number of iterations (spin count) the bit field of synchronization objects should be atomically tested and updated, before the wait on lock will start using the kernel event
			/// \return The function will return NJ_TRUE on success or NJ_FALSE in case of internal error
			virtual NjBool GetSpinCount(NjUint & uSpinCount) = 0;

		private:
			NJ_DISABLE_COPY(Implementation);
		};

		/// \copydoc NjEventsSystem::Implementation::CreateEvent
		static Event * CreateEvent(NjBool bSet);

		/// \copydoc NjEventsSystem::Implementation::TryWaitEvent
		static TryResult TryWaitEvent(Event * pEvent, TimeMs uTimeMs);

		/// \brief Waits until the kernel event is set to non-blocking state
		///
		/// This function is similar to TryWaitEvent but it cannot timeout
		/// \return NJ_TRUE if the wait succeed or NJ_FALSE if internally fails
		static NjBool WaitEvent(Event * pEvent);

		/// \copydoc NjEventsSystem::Implementation::SetEvent
		static NjBool SetEvent(Event * pEvent);

		/// \copydoc NjEventsSystem::Implementation::UnsetEvent
		static NjBool UnsetEvent(Event * pEvent);

		/// \copydoc NjEventsSystem::Implementation::DestroyEvent
		static NjBool DestroyEvent(Event * pEvent);

		/// \copydoc NjEventsSystem::Implementation::GetCurrentThreadDebugId
		static NjBool GetCurrentThreadDebugId(NjUint & uDebugId);

		/// \copydoc NjEventsSystem::Implementation::GetTimeMs
		static NjBool GetTimeMs(TimeMs & uTimeMs);

		/// \copydoc NjEventsSystem::Implementation::GetSpinCount
		static NjBool GetSpinCount(NjUint & uSpinCount);

		NJ_DECLARE_STATIC_INTERFACE_METHODS();

	private:
		NJ_DISABLE_INSTANCE(NjEventsSystem);
	};

#	include "NjEventsSystem_inline.h"

#endif // NJ_EVENTS_SYSTEM_H
